package crmdna.member;

import static crmdna.common.AssertUtils.ensure;
import static crmdna.common.AssertUtils.ensureNotNull;
import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.cmd.Query;

import crmdna.client.Client;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.user.User;

public class MemberLoader {

	public static MemberEntity safeGet(String client, long memberId,
			String login) {

		Client.ensureValid(client);
		User.ensureValidUser(client, login);

		MemberEntity entity = ofy(client).load().type(MemberEntity.class)
				.id(memberId).get();

		if (null == entity)
			throw new APIException(Status.ERROR_RESOURCE_NOT_FOUND,
					"There is no member with id [" + memberId + "]");

		return entity;
	}

	public static MemberProp safeGetDetailedInfo(String client, long memberId,
			String login) {

		Client.ensureValid(client);
		User.ensureValidUser(client, login);

		MemberProp memberProp = MemberLoader.safeGet(client, memberId, login)
				.toProp();

		// populate programProps and practices
		MemberProp.populatePractices(client, Utils.getList(memberProp));
		MemberProp.populateProgramProps(client, Utils.getList(memberProp));
		MemberProp.populateGroups(client, Utils.getList(memberProp));

		// TODO: mark that this user has viewed this member to show google drive
		// like 'Recent' menu

		return memberProp;

	}

	public static List<MemberEntity> get(String client, List<Long> memberIds,
			String login) {

		Client.ensureValid(client);
		User.ensureValidUser(client, login);

		Map<Long, MemberEntity> map = ofy(client).load()
				.type(MemberEntity.class).ids(memberIds);

		List<MemberEntity> list = new ArrayList<>(map.size());
		for (Long memberId : map.keySet()) {
			list.add(map.get(memberId));
		}

		return list;
	}

	public static List<MemberProp> querySortedProps(
			MemberQueryCondition condition, String login) {

		List<MemberProp> props = queryProps(condition, login);

		Collections.sort(props);

		return props;
	}

	static List<MemberProp> queryProps(MemberQueryCondition condition,
			String login) {

		Collection<MemberEntity> entities = queryEntities(condition, login);

		List<MemberProp> props = new ArrayList<>(entities.size());
		for (MemberEntity memberEntity : entities) {
			props.add(memberEntity.toProp());
		}

		return props;
	}

	static Collection<MemberEntity> queryEntities(
			MemberQueryCondition condition, String login) {

		List<Key<MemberEntity>> memberKeys = queryKeys(condition, login);

		Map<Key<MemberEntity>, MemberEntity> map = ofy(condition.client).load()
				.keys(memberKeys);

		return map.values();
	}

	static List<Key<MemberEntity>> queryKeys(MemberQueryCondition condition,
			String login) {

		final int MAX_RESULT_SIZE = 20000;

		Client.ensureValid(condition.client);
		User.ensureValidUser(condition.client, login);

		ensure(condition.maxResultSize > 0);
		ensure(condition.maxResultSize <= MAX_RESULT_SIZE, "maxResultSize ["
				+ condition.maxResultSize + "] should be less than ["
				+ MAX_RESULT_SIZE + "]");

		ensureNotNull(condition, "MemberLoadCondition cannot be null");

		Query<MemberEntity> q = ofy(condition.client).load().type(
				MemberEntity.class);

		if ((null != condition.groupIds) && (condition.groupIds.size() != 0))
			q = q.filter("groupIds in", condition.groupIds);

		if ((null != condition.practiceIds)
				&& (condition.practiceIds.size() != 0))
			q = q.filter("practiceIds in", condition.practiceIds);

		if ((null != condition.programIds)
				&& (condition.programIds.size() != 0))
			q = q.filter("programIds in", condition.programIds);

		if ((null != condition.firstName3Chars)
				&& (condition.firstName3Chars.size() != 0))
			q = q.filter("firstName3Char in", condition.firstName3Chars);

		if (null != condition.email)
			q = q.filter("email in", condition.email.toLowerCase());

		if (null != condition.searchStr) {
			if (condition.searchStr.length() < 3)
				Utils.throwIncorrectSpecException("Search string when specified should be atleast 3 chars");
			condition.searchStr = condition.searchStr.toLowerCase();
			Set<String> searchTags = Utils.getQSTags(condition.searchStr);
			for (String tag : searchTags) {
				q = q.filter("qsTags", tag);
			}
		}

		List<Key<MemberEntity>> memberKeys = q.keys().list();

		if (memberKeys.size() > condition.maxResultSize) {
			throw new APIException().status(Status.ERROR_OVERFLOW).message(
					"Found [" + memberKeys.size()
							+ "] matches. (Max allowed is ["
							+ condition.maxResultSize + "])");
		}

		return memberKeys;
	}
}
